loader_root = File.dirname(__FILE__)

require loader_root+"/../magic/magic"
require loader_root+"/matchlist"
require loader_root+"/camel_case"
require loader_root+"/dependency_solver"
require loader_root+"/file_seeker"

module Loader
  
  #Loads files into the enviromnent solving dependencies
  class Loader

    attr_accessor :blacklist, :whitelist, :seek
    
    def initialize
      @d_solver = DependencySolver.new()
      @seek = FileSeeker.new
      @blacklist = seek.blacklist
      @whitelist = seek.whitelist
      @file_infos = {}
    end
    
    def set_root root
      @root = root
      seek.root = root
    end
    
    # reaload each loaded file
    def reload
      changed_ones = []
      @file_infos.each_pair do |ns, infos|
        infos.each_pair do |filename, info|
          if info[:time] != File.mtime(info[:file])
            __load_file info[:file], filename, ns
            changed_ones << info[:file]
          end
        end
      end
      changed_ones
    end
    
    # Gets the list of loaded files
    def loaded_files
      lf = []
      @file_infos.each_pair do |ns, infos|
        lf.push(*infos.collect { |k, v| v[:file] })
      end
      lf
    end
    
    # Gets the path for specific filename
    def path_of filename, namespace = nil
      if filename.is_a? Class
        namespace ||= Magic.module_of filename
        filename = CamelCase.underscore(filename.to_s.split('::').last)+'.rb'
      end
      
      if @file_infos[namespace] && @file_infos[namespace][filename]
        return @file_infos[namespace][filename][:file]
      end
      paths = []
      @file_infos.each_pair { |ns, infos|
        if infos[filename]
          paths << infos[filename][:file]
        end
      }
      if paths.length == 1
        return paths[0]
      elsif paths.length > 1
        raise "Found many paths for file #{filename}:\n #{paths.join("\n\t")}"
      end
      raise "No path found for #{filename}, in any namespace."
    end
    
    def file_of(*args)
      path_of(*args)
    end
    
    def dir_of file, namespance = nil
      File.dirname(path_of(file, namespance))
    end
    
    # loads one file
    def load file, namespace = nil
      if file[-3,1] != '.'
        file += ".rb"
      end
      unless file[1,1] == ":" or File.exists? file
        file = File.join(@root + file)
      end
      @file_infos[namespace] ||= {}
      _load_file file, File.basename(file), namespace
    end
    
    # loads directory recursively,
    #  solving class dependencies
    def load_dir dir, namespace = nil
      if namespace
        namespace = namespace.to_s
      end
      @file_infos[namespace] ||= {}
      unsolved = []
      #first try to load all and collect dependencies
      files = seek.files_under_dir(dir)
      exception = nil
      files.each_pair { |filename, file|
        begin
          #puts "trying to load '#{filename}'"
          _load_file file, filename, namespace
        rescue NameError => e
          exception = e
          other_filename = CamelCase.underscore(e.name.to_s.split('::').last.to_s)+'.rb'
          unsolved << [filename, file]
          #puts "#{filename} needs '#{other_filename}'"
          @d_solver.add_dependency(filename, other_filename)
        end
      }
      unsolved.each do |filename, file|        
        dependency_list = @d_solver.solve_dependencies(filename)
        puts "#{filename} solved with #{dependency_list.inspect}"
        dependency_list.each { |dependency_file_name|
          if dep_file = files[dependency_file_name]
            _load_file dep_file, dependency_file_name, namespace
          else
            begin
              # Try on more time. The dependence that we could not find
              # might have been already loaded.
              _load_file file, filename, namespace
            rescue Exception => exception
              raise DependencySolver::MissingDependencyException.new("Missing dependency #{dependency_file_name}.", exception)
            end
          end
        }
        _load_file files[filename], filename, namespace
      end
    end

    private

    def _load_file file, filename, namespace
      #puts "loading #{file}"
      @file_infos[namespace][filename] ||= {:file => file}
      __load_file file, filename, namespace
    end
    
    def __load_file file, filename, namespace
      Kernel.load(file)
      @file_infos[namespace][filename][:time] = File.mtime(file)
      return true
    end
  end


  #singleton
  class Load < Magic::Singleton
    self.singleton_instance= Loader.new
    
    def self.file file
      self.load file
    end
  end

end